كيفية إرسال بريد إلكتروني بدوال
Winsock API
في البداية
يجب أن تعرف إن الموضوع ليس في إرسال البريد
الإلكتروني من لغة
برمجة معينة.. لكن الموضوع يكمن في كيفية استخدام بروتوكول SMTP من أي لغة
برمجة لمخاطبة خادم البريد المرسل إليه وكيف ترد عليه إذا
أعطاك رسالة معينة وهكذا ..
وفيما يلي شرح مبسط لكيفية استخدام بروتوكول SMTP
Simple Mail Transfer Protocol (SMTP) :
كثيرا ما تسمع أسم
هذا البروتوكول وأنت تتعامل مع برامج الهاك بالذات .. حيث يستخدم هذا البروتوكول في إرسال أيميل عن طريق الاتصال بسيرفر الموقع SMTP
Server
- الموقع المضيف للأيميل أي المراد إرسال البريد الإلكتروني إلي إيميل
مسجل علي السيرفر نفسه - علي المنفذ 25 ... في الواقع أنت ها
هنا تتصل مع برنامج يدعم بروتوكول SMTP مثل Mdaemon أو أي
برنامج أخر موجود علي السيرفر ويستخدم لإدارة حركة
الاتصال بين أي عميل والسيرفر عن طريق هذا البروتوكول...
حيث البرنامج هو الذي يرد عليك .. في كل خطوة..
وبروتوكول SMTP يدعم
مجموعة خطوات واحدة لإرسال أي بريد.. أي أن يلعب دور
المنظم لعملية الإرسال لبريد معين..
هناك أسلوب قديم نوعا لإرسال
رسائل مزيفة إلى أيميل ما بطريقة يدوية .
حيث تكتب من أل Telnet في نظام النوافذ Windows)) في خانتي Host و port عنوان خادم البريد والمنفذ 25.ولاحظ أنك لن تري ما تكتبه في تطبيق Telnet وبالتالي يجب تشغيل خدمة local echo من الإعدادات Preferences .
Telnet mail.hotmail.com 25
=========================================
helo Hotmail
Rem
اسم أل أيميل المرسل منه
MAIL FROM : <Thensync@hotmail.com>
Rem فيرد علينا خادم البريد بالرسالة التالية
250
Thensync@hotmail.com... Sender ok
Rem ثم نكتب أسم البريد المستهدف
RCPT TO: <Victim Mail@hotmail.com>
Rem
ويرد علينا خادم البريد
بالرسالة التالية
250
victim Mail@hotmail.com... Recipient ok
Rem
Enter على سطر لوحدها ونضغط dataثم نكتب
data
Rem فيكون رد خادم الموقع هو
354 Enter mail, end with "." on a line by
itself
Rem وإذا ظهر رد الخادم السابق فتكتب عنوان الرسالة ثم نص الرسالة
Subject: Test Fake Mail
Hi , I am Testing A fake mail
Rem لإرسالها Enterوتكتب نقطة على سطر لوحدها لإنهاء الرسالة وتضغط
.
Rem
ويرد عليك خادم البريد بالرد التالي إذا تمكن من إرسال الرسالة
250 CAA15314 Message accepted for delivery
Remومن ثم لو أردت الخروج تكتب
Quit
=========================================
الأرقام
التي تسبق الردود مثل 250 هي أرقام تعريفية للرسائل Message ID أو (MID)
وستجد لها استخدام مهم جدا في موضوعنا هذا..
وإذا
لخصت لك طريقة إرسال أل أيميل باستخدام بروتوكول SMTP مرة أخرى من أل Telnet :
MAIL FROM: <Thensync@hotmail.com>
RCPT TO: <Victim Mail@hotmail.com>
data
Subject: Test Fake Mail
Hi , I am Testing A fake mail
.
هل
هناك أسهل من هذا ..ولهذا أطلق علي البروتوكول Simple.طبعا يمكنك إضافة معلومات أخري إلى الرسالة
مثل أسم المرسل وأسم المرسل إليه والتاريخ ونوع الرسالة... إلخ
لكننا
قد كتبنا ما يسمح لنا بإرسال البريد فقط وهذه الخطوات واحدة للتعامل مع البروتوكول من أي برنامج تعرفه يرسل
أيميل ويجب مراعاة تلك الخطوات والترتيب عندما نستخدمها نحن من داخل
برنامجنا, الذي سنقوم بكتابته
وكما لاحظت فلابد أن نرسل hello
لخادم البريد.. لكي نعرف هل سيستجيب لما سيرسل إليه أم لا؟ ثم كتابة البريد المرسل منه ,
ثم كتابة بريد المرسل إليه ثم كتابة جسم الرسالة ولبدء الكتابة نكتب Data ثم نكتب عنوان الرسالة فمضمون الرسالة ثم نقطة لإنهاء
الرسالة.
وكل
حدث تكتبه سيكون له رد من خادم البريد فمثلا إذا استقبلت :
220 : مجرد
معلومات عن خادم البريد الذي اتصلت به وتستقبل ذلك الرقم عند اتصالك مع خادم البريد SMTP Server .
250 : يدل
علي نجاح الخطوة -(الأمر)
- السابقة عند الخادم.
354 : بدء
كتابة جسم الرسالة.
221:
يدل علي أن خادم البريد قد قطع الاتصال مع برنامجنا وهذا يحدث عندما ننهي اتصالنا
بالخادم باستخدام Quit والمفروض أن عند تنفيذ Quit أن تكون الرسالة قد تم إرسالها.
451, 452, 500, 550, 551 : أخطاء غير متوقعه عند خادم
البريد ..

أرقام MID موضحة
كالأتي :
|
211 System status, or system
help reply 214
Help message [Information
on how to use the receiver or the meaning of a
particular non-standard command; this
reply is useful only to the human user] 220
<domain> Service ready 221
<domain> Service closing transmission channel [This
may be a reply to any command if the service knows it must shut down] 250
Requested mail action okay, completed 251
User not local; will forward to <forward-path> 354
Start mail input; end with <CRLF>.<CRLF> 421
<domain> Service not available, closing transmission channel 452
Requested action not taken: insufficient system storage 450
Requested mail action not taken: mailbox unavailable [E.g., mailbox busy] 451
Requested action aborted: error in processing 500
Syntax error, command unrecognized 501
Syntax error in parameters or arguments 502
Command not implemented 503
Bad sequence of commands 504
Command parameter not implemented 550
Requested action not taken: mailbox unavailable [E.g.,
mailbox not found, no access] 551
User not local; please try <forward-path> 552
Requested mail action aborted: exceeded storage allocation 553
Requested action not taken: mailbox name not allowed [E.g.,
mailbox syntax incorrect] 554
Transaction failed |
طيب في أثناء شرح دوال أل Winsock API لم نذكر جميع دوال تلك المكتبة حيث هناك دوال أخري مهمة مثل دالة GetHostName ودالة
gethostbyname الأولي تأتي لك باسم الجهاز أما الثانية فعند
إرسالك للبريد افترضنا أنك كتبت خادم البريد SMTP
Server والذي
ستتصل به عبر بروتوكول SMTP مثلا مثل موقع Hotmail في المثال السابق كتبنا له ملقم
البريد mail.hotmail.com- ولا أعلم لماذا أطلقوا عليه ملقم لكن المقصود به
هو السيرفر Server- وكل خادم بريد هو جهاز
كومبيوتر في الواقع متصل بالشبكة وله
عنوان IP .. ولكي نتصل
بهذا الخادم يجب أن نعرف عنوان أل IP لجهاز الخادم, ولذلك سنستخدم دالة gethostbyname والتي تتيح لنا معرفة رقم العنوان IP بدلالة أسم ملقم الخادم SMTP Server.
وتصريح تلك الدالة :
Declare Function gethostbyname Lib
"ws2_32.dll" (ByVal
host_name As String) As Long
إذا لم تنجح تلك الدالة فهي تعود بالقيمة صفر .
ستجد مثال جميل جداً في برنامج API Guide -وهو بالمناسبة من أفضل وأسرع وأسهل المصادر لدوال API - والذي يمكنك تحميله من موقع www.allapi.net.
المثال ستجده بأسم gethostbyname وبه يعود بعنوان أل IP المحلي بدلالة اسم الجهاز واسم
الجهاز تستطيع الحصول بواسطة الدالة gethostname,وهنا يلعب
اسم الجهاز بالضبط دور اسم الموقع أو اسم خادم البريد.
نفس المثال بالضبط الذي ستحصل عليه من برنامج API
Guide تستطيع أن
تحصل عليه من مكانه الأصلي بمقاله في Microsoft Knowledge Base رقمها 160215 وبعنوان :
"How To Obtain the Host IP Address Using Windows Sockets"
أو انسخ العنوان التالي:
http://support.microsoft.com/default.aspx?scid=kb;EN-US;160215
المعامل الوحيد لدالة gethostbyname هو host_name ونعوض عنه باسم الموقع أو اسم خادم البريد
الذي نريد عنوانه.
وتستقبل تلك الدالة -إذا نجحت- معلومات عن عنوان الإنترنت المطلوب ومنها رقم أل IP وتخزن في بنية HOSTENT ولذلك تعتبر تلك الدالة من
دوال Database Functions لأنها تحتاج إلى حجز Structure أو بنية لتخزين المعلومات.
Type
HOSTENT
hName
As Long 'أسم الموقع
hAliases
As Long ' Host مصفوفة يخزن بها أسماء أخري بديلة
للموقع لو كان
hAddrType
As Integer
'عنوان العائلة للبروتوكول
hLength
As Integer 'الطول بالبايت لكل
عنوان
hAddrList
As Long
' أو خادمIP هذا لو كان الموقع
له أكثر من عنوان IP(s)قائمة بعناوين
الانترنت
End Type
_________________________________________________________________________________
وقبل استخدام دالة gethostbyname يجب أن تلقي نظرة على كيفية جلب العنوان
الذي سيخزن في
البنية HOSTENT وأرجو أن تكمل الموضوع للنهاية - هذا في حالة إذا لم تكن قد تعاملت مع لغة السي, أو سبق لك التعامل مع
المؤشرات Pointers
في لغة الفيجوال بيسيك.. أما
إذا سبق لك التعامل مع أي منهم فيمكنك ملاحظة الموضوع فقط والمرور عليه إلي بداية
خطوات إرسال البريد- .
فتلك البنية HOSTENT تعتبر في الواقع مؤشر أو (Pointer) .والمؤشر
هو متغير يخزن في مكان في الذاكرة ويتكون المؤشر من اثنين .
الأول قيمة المؤشر أو المتغير Pointer Value والثاني موقعه في الذاكرةPointer Address .
وعند استخدام دالة
gethostbyname بنجاح فهي تعود بمؤشرات أو بمتغيرات تحتوي
على معلومات للموقع المطلوب تخزن في
البنية HOSTENT, وضمن تلك
المعلومات سنجد عنوان أل IP للموقع.
ولكي يتم الحصول علي عنوان أل IP من المؤشر الموجود في الذاكرة يجب استخدام دالة تتعامل مع الذاكرة مثل CopyMemory .
وأرجو أن تأخذ بالك من تلك النقطة لكي نكون أكثر تحديدا
:
دالة gethostbyname لا تعود بمعلومات بنية HOSTENT ولكنها
تعود بمؤشر (Pointer) لتلك البنية ..
أي أننا لا نستطيع أن نقول للحصول علي عنوان ال IP :
strIP= HOSTENT.hAddrList
وفي
الأصل هذا
المؤشر يشير إلى عنوان القيم التي عادت من جراء استخدام الدالة gethostbyname (ومنها
عنوان أل IP) الذي نريده.
وهكذا تتخذ المؤشرات شكل القوائم المتصلة Linked
list
وسيكون عنوان أل IP للخادم مخزن في المؤشر
hAddrList من بنية HOSTENT وكما قلت سابقا سنستخدم دالة
مثل CopyMemory أو RtlMoveMemory .
وتصريح تلك الدالة :
Declare Sub CopyMemory Lib
"kernel32.dll" Alias "RtlMoveMemory"
(Destination As Any, Source As _ Any, ByVal
Length As Long)
وتستخدم تلك الدالة لنقل جزء من الذاكرة
من مكان لأخر.وطبعا المكانين سنعوض عنهم في عناوين الذاكرة بالمؤشرات .
====================================================================================
Destination
:
مؤشر
لعنوان الذاكرة والذي سيستقبل البيانات المنقولة إليه .
====================================================================================
Source :
مؤشر
لعنوان الذاكرة والذي سيتم منه نقل البيانات إلى المؤشر الأول Destination .
====================================================================================
Lenght :
عدد
الباتات (Bytes) التي سيتم نقلها من أل
Source إلى أل Destination .
====================================================================================
وكمثال سريع فقط علي استخدام دالة CopyMemory :
Private Declare Sub CopyMemory
Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source
As Any, ByVal Length As Long)
Private Sub Command1_Click()
Dim A As
Integer
A = 5000
CopyMemory
A, 32000, 2
MsgBox
A
End Sub
في المثال السابق إذا قمت بتنفيذه فستجد قيمة المتغير A قد أصبحت ب 32000 وذلك لأننا تعاملنا معه علي أنه مؤشر وغيرنا قيمته
من الذاكرة باستخدام دالة CopyMemory وبدون وضع "A=32000" .
ولاحظ أننا وضعنا حجم البيانات التي نريد نقلها ب
2 ويمكن تمثيل ال 2 بايت بقيمة في حدود حجم المتغير من
نوع Integer لوضعها في متغير من نوع Integer وهو A, ولكن إذا زادت القيمة من 32000 إلي
مثلا 40000 فسيحدث Overflow وسيأخذ المتغير قيمة ليست قيمته
الحقيقة وبالتالي ينبغي تغيير حجم كلا من المتغير A ليصير Long بحجم 4 بايت.. وكذلك تغيير حجم
البيانات المنتقلة ليصير 4 بايت.
==============================================================
وسأقوم الآن بشرح كيفية تمثيل أنواع البيانات المختلفة في الذاكرة وكيفية معرفة
عنوان الذاكرة لمتغير ما:
ولاحظ أن هذا الكلام مهم جدا حتى تستطيع فهم المؤشرات Pointer
==============================================================
استخدام VarPtr :
دالة VarPtr أو Variable Pointer تستخدم للحصول
علي عنوان الذاكرة Address Memory لمتغير رقمي أو لعنصر في مصفوفة Array element رقمية ولها
معامل Parameter وحيد
ويعوض عنه باسم المتغير أو عنصر المصفوفة والذي نريد معرفة عنوانه في الذاكرة..
مثال :
|
Private Sub Form_Click() Dim lngVarAddress As Long Dim intVar As Integer lngVarAddress = VarPtr(intVar) Print "Address
Memory of the Variable intVar is : " ;lngVarAddress End Sub |
في المثال السابق عرفنا متغير وليكن lngVarAddress من نوع Long أي أنه 4 بايت أي أنه يستطيع حمل أرقام signedفي
المدي :
Long Variable Signed : -2147483648 to 2147483647
وهذا
لأن عناوين الذاكرة تكون أرقام كبيرة نوعا.. أكبر من أن يتحملها متغير من نوع Integer فسيحدث Overflow لأن
مداها :
Short Or Integer Variable signed: -32768 to 32767
المهم بعد ذلك عرفنا متغير intVar من نوع Integer .. وهذا المتغير هو ما سنقوم بمعرفة عنوان الذاكرة
له عن طريق استخدام الدالة VarPtr..ثم قمنا بطباعة عنوان
الذاكرة له علي الفورم... والصورة التالية ستكون شبيهة
بما سيحدث عندك..

الآن سنقوم بمعرفة عناوين الذاكرة لمصفوفة Fixed
عدد حدودها عشرة وهذا لسبب ستعرفه في حينه :
المثال التالي يقوم بطباعة عناوين الذاكرة لمصفوفة MyArray(9) من نوع integer علي الفورم :
|
Private Sub Form_Click() Dim lngVarAddress As Long Dim MyArray(9) As Integer For I =
LBound(MyArray) To UBound(MyArray) lngVarAddress = VarPtr(MyArray(I))
Print "Address Memory of the MyArray(";
I; ")"; " is : "; lngVarAddress Next I End Sub |
بعد تنفيذ الكود لاحظ الصورة التالية ثم قم بالاستنتاج :

بالضبط كما استنتجت أنت ..
عندما حجزنا مصفوفة من نوع Integer – 2 بايت- وعدد حدودها عشرة.. قام بحجز 20 بايت في
الذاكرة.. وقد قمنا بمعرفة عناوين عناصر تلك المصفوفة في الذاكرة حسب أل Base ...
وإذا كانت المصفوفة من نوع Long - 4 بايت- مثلما توجد في هذا
الكود :
|
Private Sub Form_Click() Dim lngVarAddress As Long Dim MyArray(9) As Long For I =
LBound(MyArray) To Ubound(MyArray) lngVarAddress = VarPtr(MyArray(I))
Print "Address Memory of the MyArray(";
I; ")"; " is : "; lngVarAddress Next I End Sub |
فسيتم حجز 40 بايت :

========================================
كل ما سبق حتى الآن جميل ...بل
غاية في الجمال.. لكن ماذا سنستفيد من كل هذا ؟
الصراحة لا أعلم إذا كانت تحسب علي لغة الفيجوال بيسيك سهولة التعامل مع
المتغيرات والمصفوفات من وإلي الذاكرة ..حتى أن هناك
الكثير لم يسمع أبدا عن وجود المؤشرات Pointers في لغة الفيجوال
بيسيك.. لكن أعتقد أن هذا أفضل وأكثر سهولة لهذه اللغة
وسنأخذ المثالين التاليين لتعرف الفرق :
سنعمل علي مثال المصفوفة ذات الحدود العشرة السابقة مع
تحوير بسيط :
المثال
الأول:
نقوم فيه بإسناد قيم للمصفوفة وإرجاع تلك القيم بالطريقة
التقليدية :
|
Private Sub Form_Click() Dim lngVarAddress As Long Dim MyArray(9) As Long For I =
LBound(MyArray) To Ubound(MyArray) MyArray(I) = I lngVarAddress = VarPtr(MyArray(I))
Print "Address Memory of the MyArray(";
I; ")"; " is : "; lngVarAddress
;
Print "| Value = " ; MyArray(I) Next I End Sub |

المثال الثاني :
نقوم فيه بإسناد القيم للمصفوفة وإرجاعها عن طريق
استخدام دالة CopyMemory والتي سنقوم
بنسخ محتوي المؤشر أو عنوان الذاكرة إلي
متغير لدينا :
|
Private Sub Form_Click() Dim lngVarAddress As Long Dim MyArray(9) As Integer Dim intVal As Integer For I =
LBound(MyArray) To UBound(MyArray) MyArray(I) = I lngVarAddress = VarPtr(MyArray(I))
Print "Address Memory of the MyArray(";
I; ")"; " is : "; lngVarAddress;
" "; CopyMemory intVal, ByVal (lngVarAddress), 2
Print "| Value = "; intVal Next I End Sub |
ونفس الصورة السابقة ستظهر مع اختلاف عناوين الذاكرة (عناوين
المؤشرات) كل مرة :

لاحظ هنا
أنه عندما استخدمنا دالة CopyMemory وضعنا لها عنوان الذاكرة (عنوان
المؤشر) lngVarAddress في
المعامل الثاني والذي نريد نسخ محتوي بياناته من عنصر مصفوفة حجمه 2 بايت إلي
متغير من نوع Integer حجمه
2 بايت وهو intVal ولذلك
وضعنا حجم البيانات المراد نسخها 2 بايت لأنه
حجم عنصر المصفوفة element المنسوخ منه البيانات .
سأضع الآن مثال علي كيفية نسخ حدود مصفوفة من
الذاكرة إلي مصفوفة أخري... وأرجو أن تركز في هذا المثال جيدا ... لأنك ستستخدم
نفس الفكرة فيما بعد ..
اسم المثال سيكون (نسخ
مصفوفة من الذاكرة) وهذا لأنني سأذكرك بالرجوع إلي هذا المثال فيما بعد..
المهم شفرة المثال ستكون كالأتي
:
|
Private Declare Sub CopyMemory
Lib "kernel32" Alias "RtlMoveMemory"
( Destination As Any, Source As Any, ByVal Length
As Long) Private Sub Form_Click() Dim
Array1(9) As Long ' Original first Array Dim z As
Long ' just a counter Dim
Array2() As Long ' Second Array that will hold data of Array1
in it ReDim Array2(9) ' <<
setting some values to Array1 >> For z = 0
To UBound(Array1) Array1(z)
= 2 * z Next ' << Copy data from Array1 to Array2
>> CopyMemory Array2(0), Array1(0), 40 ' << Print the bounds values of Array2 >> Debug.Print String(19, "=") Debug.Print "Array1" & Space(5) &
"Array2" Debug.Print String(19, "=") Debug.Print '
Free line For z = 0
To UBound(Array2) Debug.Print Space(3); Array1(z); Space(5); Array2(z) Next z End Sub |
المثال بسيط جدا.. وفيه قمنا بتعريف مصفوفة Array1 من 10 حدود من نوع Long أي 4 بايت لقيمة الحد, معني ذلك
كما استنتجت أنت وبحسبة بسيط
(4*10) أن المصفوفة قد أخذت مساحة قدرها 40
بايت لـ 10 حدود , وقمنا بتعريف مصفوفة أخري Array2 أيضا من 10 حدود ومن نوع Long
وبعد ذلك قمنا بوضع قيم في المصفوفة Array1 حتى لا تكون قيم
الحدود بصفر .
ثم قمنا بنسخ حدود المصفوفة Array1 إلي Array2 بواسطة الكود التالي
:
CopyMemory
Array2(0), Array1(0), 40
لاحظ أننا وضعنا حجم البيانات المراد نسخها 40 بايت,
وبالتالي فسيتم نسخ القيم منذ أول بايت في المصفوفة Array1 حتى أخر بايت في
أخر حد للمصفوفة Array1 ..
لاحظ أيضا أن المصفوفة Array2 قمنا بحجز 40 بايت لها أيضا في الذاكرة... وبالتالي فسيتم نسخ جميع حدود المصفوفة Array1 إليها...
لاحظ أيضا أننا إذا أردنا نسخ خمس حدود فقط إلي
المصفوفة Array2 ..فسيكون حجم البايتات المراد نسخها 20
وهكذا...
لاحظ أيضا أنك فيما بعد ستحتاج إلي نسخ ال IP في
هيئة مصفوفة من الذاكرة.. ولهذا كتبت هذا المثال...
======================
كان يمكننا بالطبع أن نعوض عن Array1(0) في الكود :
CopyMemory Array2(0), Array1(0), 40
نعوض عنه بعنوان الذاكرة (المؤشر) لأول حد في
المصفوفة Array1(0) ..
هل تستطيع أن تأتي بعنوان الذاكرة لأول حد في تلك
المصفوفة ؟
أكيد ستستخدم دالة VarPtr هكذا :
CopyMemory Array2(0), VarPtr(Array1(0))
, 40
انتظر لحظة فالكود السابق به خطأ شديد ..
هل استنتجته ؟
هل نسيت أنك تعوض بقيمة المؤشر وليس بعنوان
المؤشر وبالتالي فلابد من وضع ByVal ليصبح :
CopyMemory Array2(0), ByVal (VarPtr(Array1(0))), 40
المهم أنه باستخدام أي طريقة لابد أن تظهر لك تلك
الصورة في شاشة ال Immediate window

ومن ناحية الاستفادة الفعلية
من دالة CopyMemory في موضع
إرسال البريد فهي ستستخدم في آتيان عنوان الإنترنت IP من المؤشر فكما قلت سابقا عندما نستخدم دالة GetHostByName فهي تعود بمؤشر
. سنقوم نحن بنسخ هذا المؤشر بعناصره والذي
عادت به الدالة إلى البنية HOSTENT باستخدام دالة CopyMemory حتى
نستطيع استخلاص عنوان الأنترنت من داخل البنية HOSTENT ...
والإجراء التالي يقوم باستخلاص عنوان الانترنت IP من البنية HOSTENT ولاحظ أنني سأضع
الكود أولا ثم أشرحه وأرجو أن تلقي نظرة علي الكود:
ملحوظة : يوجد مع المرفقات برنامج لإتيان عنوان الانترنت IP لأي SMTP Server .
Public Function GetIPAddress(sHostName As String) As String
Dim lpHost As Long
Dim
HOST As HostEnt
Dim dwIPAddr As Long
Dim tmpIPAddr()
As Byte
Dim sIPAddr As String
sHostName = Trim$(sHostName)
1 lpHost = gethostbyname(sHostName)
If lpHost = 0 Then
GetIPAddress = ""
MsgBox "Unable to successfully get Host Name..May be The SMTP Server Is
Not Exist.." & _
"Or You are Offline ."
Exit
Function
End If
2 CopyMemory HOST, lpHost, Len(HOST)
3 CopyMemory dwIPAddr, HOST.h_addr_list, 4
4 ReDim tmpIPAddr(1 To 4)
5
CopyMemory tmpIPAddr(1),
ByVal (dwIPAddr), 4
| For i = 1 To 4
6| sIPAddr = sIPAddr & tmpIPAddr(i) & "."
| Next
7
GetIPAddress = Mid$(sIPAddr,
1, Len(sIPAddr) - 1)
End Function
ولاستدعاء الإجراء السابق نكتب مثلا
:
Call GetIPAddress("mx1.mail.yahoo.com")
============================================================================
تعالي الآن نرتب معلوماتنا في
نقط مبسطة والخاصة بإتيان أل IP
لأي موقع:
< بالتالي
إذا لم تنجح الدالة لأي سبب فستعود بالقيمة صفر... وبالتالي يمكننا الخروج من
الإجراء بــ Exit Function
< أما إذا نجحت فستعود بمؤشر lpHost يحتوي علي مؤشرات داخلية متفرعة ..
الآن أصبحت عندنا مؤشرات البنية HOST وقبل الغوص في شرح أي سطور
أخري ينبغي معرفة الآتي :
هناك ثلاث مؤشرات يجب المرور عبرهم للوصول إلي عنوان
الانترنت IP ..
أول مؤشر وهو مؤشر البنية HOST
نفسها أفترض أنه يساوي 8385144
ثاني
مؤشر وهو مؤشر HOST.h_addr_list أفترض أنه
يساوي 8385156
هل لاحظت الفرق بين قيمتي المؤشرين...
ستجده دائما رقم ثابت وهو 12 وبالتالي من الممكن أن نصل إلي قيمة المؤشر HOST.h_addr_list بزيادة 12 علي قيمة المؤشر Host .. – المهم
أنه ليس موضوعنا الآن-
ثالث مؤشر وهو في الواقع مؤشر إلي أول حد في عنوان ال IP والذي يحتوي علي أربع حدود بحجم 4 بايت .. ولاحظ اننا وصلنا لعنوان أول حد وبالتالي أصبح عندنا عنوان بقية
الحدود لعنوان ال IP في الذاكرة
.. معني هذا أننا إذا قمنا بنسخ تلك الأربع بايتات في مصفوفة عندنا فستحتوي
تلك المصفوفة علي عنوان ال IP ..
وبالتالي سننسخ تلك المصفوفة إلي مصفوفة
لدينا tmpIPAddr من نوع بايت بواسطة دالة CopyMemory.. وسنقوم بتجميع عنوان ال
IP في صوره Dotted , بعمل تكرار داخل حدود المصفوفة tmpIPAddr وتجميعه في صورة (000.000.000.000)....
والسطر 7 نقوم فيه بحذف أخر نقطة
والتي ستتكون في عنوان ال IP نتيجة لإضافة نقطه بعد كل حد في
المصفوفة tmpIPAddr .., وكان يمكننا طبعا استخدام دالة Left وستكون أسهل ..
وقم بالرجوع إلي مثال (نسخ مصفوفة من الذاكرة)
لمزيد من الفهم ..وستجد هنا أننا نسخنا الأربع بايتات ابتداء من عنوان الذاكرة dwIPAddr وبحجم 4 Bytesوهو حجم ال IP في الذاكرة ......... تماما مثلما فعلنا في مثال نسخ مصفوفة ذات
عشر حدود من الذاكرة إلي مصفوفة أخري ...
وكان من الممكن طبعا نسخ كل بايت علي حده هكذا :
أحذف السطر الرابع وما بعده وضع الأتي
:
|
ReDim tmpIPAddr(0) CopyMemory tmpIPAddr(0), ByVal (dwIPAddr), 1
sIPAddr = sIPAddr & tmpIPAddr(0)
& "." CopyMemory tmpIPAddr(0), ByVal (dwIPAddr + 1), 1
sIPAddr = sIPAddr & tmpIPAddr(0)
& "." CopyMemory tmpIPAddr(0), ByVal (dwIPAddr + 2), 1
sIPAddr = sIPAddr & tmpIPAddr(0)
& "." CopyMemory tmpIPAddr(0), ByVal (dwIPAddr + 3), 1
sIPAddr = sIPAddr
& tmpIPAddr(0) GetIPAddress = sIPAddr |
أكيد فهمت ما قمت بعمله.. فكل بايت من الأربع بايتات للـ IP في
الذاكرة له عنوان مؤشر يزيد كما تعرف عن عنوان المؤشر الذي قبله... كل ما فعلته هو
أن نسخت كل بايت علي حدة بدلالة عنوان مؤشره والذي يزيد بمقدار واحد عن ما قبله
وبحجم بيانات بايت واحد ..
والصورة التالية تلخص كل شيء ...
علي فرض أننا سنأتي بال Loopback IP وهو 127.0.0.1 ..

أعتقد
أن أي التباس قد زال الآن ويمكننا الانتقال إلى استكمال كتابة
البرنامج
لكن قبل أن نستكمل فلنلقي نظرة علي بنية HOSTENT في لغة السي حتى
نستنتج معلومة أخيرة في هذا المجال
|
struct hostent { char FAR * h_name; char FAR * FAR * h_aliases; short h_addrtype; short h_length; char FAR * FAR * h_addr_list; }; |
علامة * تعني مؤشر في لغى
السي, لاحظ المتغير h_addr_list في البنية السابقة..
حيث ستجد أنه مؤشر * إلي مؤشر * إلي char وحجم ال
char هو واحد بايت ...
هل سألت نفسك ما الداعي وراء وجود مؤشرين للوصول إلي
عنوان الانترنت IP ؟.. فقد كان من الممكن أن يجعلوا الوصول إلي
عنوان الانترنت عن طريق مؤشر واحد هو h_addr_list ..
في الواقع إذا نظرت إلي مواقع مثل www.hotmail.com , www.yahoo.com,www.cnn.com ستجد أن ال DNS لأي منهم له أكثر من عنوان انترنت
IP ... فكيف إذن
سنأتي بجميع عناوين الانترنت لموقع وليكن www.yahoo.com
وبالتالي
دعت الحاجة إلي أن يكون h_addr_list مصفوفة من نوع Long أي 4 بايت لكل حد لها .. ويخزن بتلك المصفوفة جميع مواقع الانترنت IPs لأي DNS في الذاكرة .
بما أن h_addr_list تعتبر مصفوفة.. فالمصفوفة في
أبسط معانيها -وأي مصفوفة في الواقع- تكون مؤشر إلي حدودها.. وهذا يوضح لماذا
استخدمنا دالة CopyMemory للمرة
الثالثة, لاستخلاص الحد الأول في المصفوفة.
فمثلا أفترض الأتي :
المؤشر h_Addr_list عنوانه هو (8485140), وكما قلت سابقا .. فبما أنه مصفوفة فالمصفوفة تعتبر مؤشر إلي حدودها
فأول حد في تلك المصفوفة هو ال IP الأول الأفتراضي والرئيسي للموقع ولتفترض أن مؤشره هو
(8485156)..
ومنه سننسخ الأربع بايتات لعنوان الانترنت IP :
h_addr_list dwIPAddr
>>>>>>>>>
(8485140) (8485156)
ثاني حد للمصفوفة h_Addr_list وهو سيكون عنوان
الانترنت الثاني للموقع
Second IP Address ونحصل
عليه بإضافة أربع بايتات إلي عنوان مؤشر h_Addr_list بما أنها من نوع Long أي 4 بايت لكل حد لتلك المصفوفة :
h_addr_list + 4 dwIPAddr
>>>>>>>>>
(8485144) (8485180)
ونحصل علي عنوان الانترنت الثاني للموقع IP بنفس
خطوات الحصول علي عنوان الانترنت الأول,و ذلك بنسخ 4
بايتات ابتداء من عنوان الذاكرة للحد الثاني وفي المثال السابق سيكون (8485180)..
وهكذا نظل في زيادة أربعة بايتات علي عنوان المؤشر
h_Addr_list لكي
نأتي بجميع عناوين الانترنت للموقع..
هكذا نكون قد انتهينا من موضوع إتيان عنوان الانترنت للـ
SMTP Server.
لم
يعد هناك أي مشكلة في إرسال البريد الإلكتروني , فسنعمل اتصال
عادي جدا علي IP للخادم وعلي
المنفذ 25
ومن ثم نرسل له محتويات البريد الإلكتروني بدالة Send () وننتظر رد الخادم .
أولا
سنكتب أكواد الاتصال مع خادم البريد المراد تمرير رسالة
عبره إلى User مشترك علي الخادم:
ولاحظ
أنني دمجت أمر الاتصال مع أمر إرسال أل أيميل وذلك تحت Command يدعي CmdSend
لاحظ
أيضا أن علي المشروع سيكون هناك Text
تدعي txtStatus
وسنستعرض بها مراحل إرسال البريد .
سيكون
هناك أيضا 4 Text هم txtServer و txtMail وtxtSubject وtxtBody وهم على الترتيب , الأولي لكتابة عنوان الخادم
(Mail SMTP Server)
والثانية لكتابة أسم البريد الذي سترسل له الرسالة ,
والثالثة والرابعة لعنوان ومضمون الرسالة .
لاحظ
أيضا أننا سنستخدم موضوع أل Hook وال SubClass وستجد أنني عملت خطاف للرسائل وأرسلت الرسائل
إلى التصنيف الفرعي يدعي WinsockMain وسيتم تحليل رسائل المقبس
بالضبط كما عرفنا من دروس تعلم Winsock
API .
لاحظ
أيضا أنني عملت أجراء فرعي لارسائل الرسائل النصية يدعي
SendText
ومررت له 3 وسيطات الأولي أسم البريد txtmail المرسل له والثانية عنوان الرسالة txtSubject والثالثة مضمون الرسالة TxtBody.
Private Sub CmdSend_Click()
Dim RemoteAddr As sockaddr_in
On Error Resume Next
LlistenSocket
= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
If LlistenSocket
= INVALID_SOCKET Then
txtStatus = txtStatus &
vbCrLf & "Unable to Create Socket"
Else
DoEvents
txtStatus = txtStatus &
vbCrLf & "Trying To Connect"
RemoteAddr.sin_family
= AF_INET
RemoteAddr.sin_port
= htons(CLng(25))
RemoteAddr.sin_addr
= inet_addr(GetIPAddress(txtServer))
lRetval = connect(LlistenSocket,
RemoteAddr, Len(RemoteAddr))
If lRetval
= SOCKET_ERROR Then
txtStatus = txtStatus &
vbCrLf & "Unable To Connect"
Exit Sub
Else
txtStatus = txtStatus &
vbCrLf & "Connected Success TO " & txtServer
End If
MsgBox
"LlistenSocket =" & LlistenSocket
WSAAsyncSelect
LlistenSocket, Form1.hwnd, ByVal
WM_SOCKET, _
ByVal FD_CONNECT Or FD_READ Or
FD_CLOSE
End If
'*****************************************
SendText txtmail, txtSubject,
txtbody 'أجراء فرعي لإرسال الرسالة
الآن سنتناقش سويا مع أوامر البروتوكول وكيفية الاستفادة من أرقام MID .
نحن طبعا لن
نستطيع إرسال أمر معين إلى خادم البريد طالما لم يتم تنفيذ الأمر الذي يليه فمثلا
أثناء كتابتنا لأول مثال باستخدام Telnet
كتبنا أيميل المصدر للرسالة وهو :
MAIL FROM: <Thensync@hotmail.com>
ولم نكتب الخطوة
التالية وهي كتابة البريد الذي سنرسل له الرسالة إلا عندما رد علينا خادم البريد
بالرسالة التالية
250 Thensync@hotmail.com...
Sender ok
ومعناها أن
الخطوة تمت بنجاح ويمكنك الانتقال إلى كتابة الخطوة التالية:
RCPT TO: <Victim Mail@hotmail.com>
المهم الآن كيف سنعرف في برنامجنا أن الخادم رد علينا علي رسالة معينة
أم لا ؟
فبكل بساطة يمكننا عمل تصنيف
لأرقام أل MID التي تسبق الرسالة مثل 250
و354 و220 و221 وتلك هي الأرقام التي تدل علي نجاح الأمر الذي أرسلته للخادم وخلاف
تلك الأرقام يدل علي أن العملية قد فشلت Failed.
بحيث
إن مرحلة إرسال الرسالة ستقسم إلى 6 مراحل وسنعرف متغير وليكن Step ومن نوع Integer
وسيحمل أرقام المراحل الستة لينفذها
بالترتيب وكل مرحلة سيتم تنفيذها أثناء استقبال مقبس
برنامجنا أي رقم MID يدل علي نجاح الخطوة
السابقة سنضيف إلى المتغير Step الرقم واحد وبالتالي سيتم تنفيذ المرحلة التي
تليها وهكذا إلى أن يتم تنفيذ أخر مرحلة
فنغلق المقبس ونسند القيمة صفر إلى المتغير Step تمهيدا لإرسال رسالة أخري .
شفرة التصنيف الفرعي WinsockMain ستكون كالأتي :
Public step As
Integer
Public Function Winsockmain(ByVal hwnd As Long, ByVal uMsg As Long, _
ByVal
wParam As Long, ByVal lParam As Long) As Long
On Error Resume Next
If uMsg = WM_SOCKET Then
Dim ReadBuffer(1024) As Byte
Dim Data As String
Select Case lParam
Case FD_CONNECT
txtStatus = txtStatus &
vbCrLf & "Connected"
Case FD_READ
lRetval = recv(wParam, ReadBuffer(0), 1024, 0)
If lRetval > 0 Then
Data = Left(StrConv(ReadBuffer, vbUnicode), lRetval)
******************************************* '
'TxtStatusسيتم وضع رسائل خادم البريد المستقبلة في
Form1.txtStatus =
Form1.txtStatus & vbCrLf & Data
Select Case Mid(Data, 1, 3)
'MID تحليل أرقام
Case 220
step = step + 1
Form1.txtStatus = Form1.txtStatus & vbCrLf
& "220 The Server is" & _
"Ready To Send Mail" & vbCrLf
Form1.txtStatus = Form1.txtStatus & vbCrLf
& "step Become " & step
Case 250, 354
step = step + 1
Form1.txtStatus = Form1.txtStatus & vbCrLf
& "step Become " & step
Case 221 'Note That If Our Socket Recieved
221 that mean Mail Sent
step = step + 1
Form1.txtStatus
= Form1.txtStatus & vbCrLf & "step Become " & step
Case 451, 452,
500, 550, 551, 552, 553, 554
'Other MID Errors
MsgBox "The Program Connected But There Is Error At The" & _
Form1.txtServer & " Mail Server "
& vbCrLf & _
"Error Code Number Is :" &
Mid(Data, 1, 3)
End Select
'*******************************************
End If
Case FD_CLOSE
Form1.txtStatus =
Form1.txtStatus & vbCrLf &
"Disconnected"
End Select
End If
Winsockmain = CallWindowProc(lpWinsock, hwnd, uMsg, _
wParam, lParam)
End Function
وبالنسبة للأجراء الفرعي SendText فقبل أن نكتبه لازم نحول البيانات من نصوص إلى مصفوفة بايتات Byte Array ANSI حتى
يستطيع خادم البريد التعامل مع البيانات المرسلة. وعبر هذا الأجراء أيضا سنرسل الأوامر إلى خادم البريد.
هناك دالة تدعي VarType أو Varible Type وتفيد تلك الدالة في معرفة نوع
المتغير (String ,
Integer ... الخ) عن طريق إرجاع قيمة تدل عليه. فمثلا إذا استخدمنا دالة VarType لمعرفة نوع متغير وعادت قيمة
الدالة بالرقم 2 فهذا يعني أن المتغير من نوع Integer . والأرقام التالية توضح بعض قيم
الأنواع المختلفة للمتغيرات .
2
= Integer
3
= Long
4
= Single
5
= Double
8
=String
11
= Boolean
12
= Variant
17
= رقم صحيح Byte
36
= متغير معرف باستخدام
Type
8192
= مصفوفة Array
ونستطيع
أن نعرف نوع المتغير ومن ثم نقوم بتحويله
إلي مصفوفة بايتات ولاحظ أننا لن نحتاج سوي لتحويل قيم Strings وهي القيم التي سنرسلها لخادم البريد
:
Public Function SendData(ByVal Sock&, Message As
String) As Long
Dim TheMsg() As Byte
TheMsg
= ""
TheMsg
= Message
TheMsg
= StrConv(Message,
vbFromUnicode)
Debug.Print
TheMsg; " | "; StrConv(TheMsg,
vbUnicode); " | "; UBound(TheMsg)
If UBound(TheMsg) > 0 Then
SendData = send(Sock, TheMsg(0), UBound(TheMsg) + 1, 0)
End If
End Function
الأجراء التالي وهو الخاص بإرسال الأوامر بالطلبات وتلك الأوامر تدعي Header وهذا أل
Header له ترتيب خاص لإرسال معلومات الرسالة والترتيب التالي يوضح ما أقول :
1- HELO anyThing 'Enter وتستخدم كبروتوكول لبدء كتابة الرسالة ثم
2- MAIL FROM: <Thensync@hotmail.com> ' Enterثم
3-RCPT TO: <AnyMail@hotmail.com> ' Enterثم
4-DATA 'Enterثم
5-
From: Mohamed Fayed 'أسم المرسل وهي معلومة إضافية ويمكن الاستغناء عنها
To: Receiver Name 'أسم متلقي الرسالة وأيضا معلومة إضافية
SUBJECT: Test Smtp Mail ' ونكتب نص الرسالةEnter عنوان الرسالة ثم
That Is My Smtp Send Mail ' Mail Body
. 'Enter نقطة لإنهاء
الرسالة حتى يتم إرسالها ثم
6-quit 'Enterالخروج ثم
وهناك معلومات إضافية أخري يمكن كتابتها
مثلا مثل التاريخ والوقت ..
المهم هذا هو الأجراء الخاص بإرسال أل أيميل:
Public Sub SendText(Mailto As String, MailSubject As String, MailBody
As String)
Msg = "HELO " & Mid(Mailto, InStr(1,
Mailto, "@") + 1) & vbCrLf
Call SendData(LlistenSocket,
"HELO " & Mid(Mailto, InStr(1, Mailto,
"@") + 1) & vbCrLf) 'send the data
Form1.txtStatus = Form1.txtStatus & Msg
While step < 1 ' لتضييع الوقت لحد ما يتم تنفيذ
كود الإرسال وهو يعتمد عليloopمجرد
DoEvents ' واحد وينتقل Stepتلقي رد من
الخادم.فإذا تلقي رد فسيضيف إلى المتغير
Wend 'لتنفيذ الكود التالي ويمكنك وضع مؤقت لكي يعرف المستخدم بأن فترة
' الانتظار وهي في معظم البرامج تقدر ب 60 ثانية قد طالت وأنه خادم
' البريد لا يرد علي الأوامر المرسلة إليه
'*****************************************
Msg = "MAIL
FROM: <" & "Thensync@hotmail.com" & ">"
& vbCrLf
Call SendData(LlistenSocket,
"MAIL FROM: <" & "Thensync@hotmail.com" &
">" & vbCrLf)
Form1.txtStatus =
Form1.txtStatus & Msg
While step < 2
DoEvents
Wend
'*****************************************
Msg = "RCPT
TO: <" & "nanosoftegypt@yahoo.com" & ">"
& vbCrLf
Form1.txtStatus = Form1.txtStatus & Msg
Call SendData(LlistenSocket, "RCPT TO:
<" & "nanosoftegypt@yahoo.com" & ">"
& vbCrLf)
While step < 3
DoEvents
Wend
'*****************************************
Msg =
"DATA" & vbCrLf
Form1.txtStatus = Form1.txtStatus & Msg
Call SendData(LlistenSocket, "DATA"
& vbCrLf)
While step < 4
DoEvents
Wend
'########################################
Msg =
"SUBJECT: " & MailSubject & vbCrLf
Form1.txtStatus = Form1.txtStatus & Msg
Call SendData(LlistenSocket, Msg & vbCrLf)
'===========================
Msg = MailBody & vbCrLf
Form1.txtStatus = Form1.txtStatus & Msg
Call SendData(LlistenSocket, Msg & vbCrLf)
'==========================
Msg =
"." & vbCrLf
Form1.txtStatus = Form1.txtStatus & Msg
Call SendData(LlistenSocket, Msg & vbCrLf)
'########################################
Msg =
"quit" & vbCrLf
Form1.txtStatus = Form1.txtStatus & Msg
Call SendData(LlistenSocket, Msg & vbCrLf)
While step < 5
DoEvents
Wend
closesocket (LlistenSocket)
MsgBox "Email
has been Sent To " & Form1.txtmail
End Sub
<<< The END >>>
Tutorial By:
Mohamed Farouk Fayed